package fr.leogomes.http2;
import static io.netty.buffer.Unpooled.wrappedBuffer;
import static io.netty.handler.codec.http.HttpHeaderUtil.setContentLength;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import static java.lang.Math.abs;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.math.NumberUtils.toInt;
import fr.leogomes.Html;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http2.HttpUtil;
import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapter;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Handles all the requests for data. It receives a {@link FullHttpRequest},
* which has been converted by a {@link InboundHttp2ToHttpAdapter} before it
* arrived here. For further details, check {@link Http2OrHttpHandler} where the
* pipeline is setup.
*
* @author Leonardo Gomes <http://leogomes.fr>
*/
public class Http2RequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
static final String LATENCY = "latency";
static final String Y = "y";
static final String X = "x";
static final String PATH = "/http2";
static final AsciiString CONTENT_TYPE = new AsciiString("Content-Type");
static final byte[] dummy = "nothing".getBytes();
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
QueryStringDecoder queryString = new QueryStringDecoder(request.uri());
String streamId = streamId(request);
// Check arguments: path must match and latency parameter must be present
if (!PATH.equals(queryString.path()) || missing(queryString, LATENCY)) {
sendDummy(ctx, streamId);
return;
}
int latency = latency(queryString);
if (missing(queryString, X) && missing(queryString, Y)) {
handlePage(ctx, streamId, latency);
} else {
handleImage(queryString, ctx, streamId, latency);
}
}
private void handleImage(QueryStringDecoder query, ChannelHandlerContext ctx, String streamId, int latency) {
int x = toInt(query.parameters().get(X).get(0), 0);
int y = toInt(query.parameters().get(Y).get(0), 0);
byte[] image = ImageCache.instance().image(x, y);
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, wrappedBuffer(image));
response.headers().set(CONTENT_TYPE, "image/jpeg");
sendResponse(ctx, streamId, latency, response);
}
private void handlePage(ChannelHandlerContext ctx, String streamId, int latency) {
ByteBuf content = Unpooled.wrappedBuffer(Html.HEADER, Html.body(latency), Html.FOOTER);
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, content);
response.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
sendResponse(ctx, streamId, latency, response);
}
protected void sendResponse(ChannelHandlerContext ctx, String streamId, int latency, FullHttpResponse response) {
setContentLength(response, response.content().readableBytes());
setStreamId(response, streamId);
ctx.executor().schedule(new Runnable() {
public void run() {
ctx.writeAndFlush(response);
}
}, latency, TimeUnit.MILLISECONDS);
}
private int latency(QueryStringDecoder queryString) {
int latency = toInt(queryString.parameters().get(LATENCY).get(0), 0);
// Make sure that latency is not too big
return abs((latency > 1000 ? 0 : latency));
}
private String streamId(FullHttpRequest request) {
return request.headers().get(HttpUtil.ExtensionHeaderNames.STREAM_ID.text());
}
private void setStreamId(FullHttpResponse response, String streamId) {
response.headers().set(HttpUtil.ExtensionHeaderNames.STREAM_ID.text(), streamId);
}
private boolean missing(QueryStringDecoder query, String string) {
List<String> values = query.parameters().get(string);
return (query.parameters() == null || values == null || values.size() == 0 || EMPTY.equals(values.get(0)));
}
private void sendDummy(ChannelHandlerContext ctx, String streamId) {
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, wrappedBuffer(dummy));
response.headers().set(CONTENT_TYPE, "text/plain");
sendResponse(ctx, streamId, 0, response);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.err.print(cause);
ctx.channel().close();
}
}